生命周期

生命周期钩子函数就是每个 Vue 实例在被创建、更新时都要经过一系列的初始化过程会触发的钩子函数。
Vue 实例从创建到销毁的过程,就是生命周期。从开始创建、初始化数据、编译模板、挂载 Dom→ 渲染、更新 → 渲染、销毁等一系列过程,称之为 Vue 的生命周期。
总共分为 8 个阶段:创建前/后, 载入前/后,更新前/后,销毁前/销毁后。
- beforeCreate(创建前) 在数据观测和初始化事件还未开始
- created(创建后) 完成数据观测,属性和方法的运算,初始化事件,
$el属性还没有显示出来 - beforeMount(载入前) 在挂载开始之前被调用,相关的 render 函数首次被调用。实例已完成以下的配置:编译模板,把 data 里面的数据和模板生成 html。注意此时还没有挂载 html 到页面上。
- mounted(载入后) 在 el 被新创建的
vm.$el替换,并挂载到实例上去之后调用。实例已完成以下的配置:用上面编译好的 html 内容替换 el 属性指向的 DOM 对象。完成模板中的 html 渲染到 html 页面中。此过程中进行 ajax 交互。 - beforeUpdate(更新前) 在数据更新之前调用,发生在虚拟 DOM 重新渲染和打补丁之前。可以在该钩子中进一步地更改状态,不会触发附加的重渲染过程。
- updated(更新后) 在由于数据更改导致的虚拟 DOM 重新渲染和打补丁之后调用。调用时,组件 DOM 已经更新,所以可以执行依赖于 DOM 的操作。然而在大多数情况下,应该避免在此期间更改状态,因为这可能会导致更新无限循环。该钩子在服务器端渲染期间不被调用。
- beforeDestroy(销毁前) 在实例销毁之前调用。实例仍然完全可用。
- destroyed(销毁后) 在实例销毁之后调用。调用后,所有的事件监听器会被移除,所有的子实例也会被销毁。该钩子在服务器端渲染期间不被调用。
详细介绍
在初始化时,会调用以下代码,生命周期就是通过 callHook 调用的
Vue.prototype._init = function (options) {
// 初始化生命周期函数
initLifeCycle(vm);
// 事件
initEvents(vm);
// 渲染函数
initRender(vm);
// 执行 beforeCreate 钩子
callHook(vm, "beforeCreate"); // 拿不到 props data
// 初始化 inject
initInjections(vm);
// 初始化 props data 之类的值。 在 init 阶段(defineReactive) Vue 对象属性 data 的属性会被 reactive 化,即会被设置 getter 和 setter 函数。
initState(vm);
// 初始化 provide
initProvide(vm);
// 执行 created 钩子。 此时已经可以拿到 props data 了
callHook(vm, "created");
};
可以发现在以上代码中,beforeCreate 调用的时候,是获取不到 props 或者 data 中的数据的,因为这些数据的初始化都在 initState 中。
接下来会执行挂载函数
export function mountComponent {
// 挂载节点之前的钩子
callHook(vm, 'beforeMount')
// ...
if (vm.$vnode == null) {
vm._isMounted = true
// 挂载节点完毕钩子
callHook(vm, 'mounted')
}
}
beforeMount 就是在挂载前执行的,然后开始创建 VDOM 并替换成真实 DOM,最后执行 mounted 钩子。这里会有个判断逻辑,如果是外部 new Vue({}) 的话,不会存在 $vnode ,所以直接执行 mounted 钩子了。如果有子组件的话,会递归挂载子组件,只有当所有子组件全部挂载完毕,才会执行根组件的挂载钩子。
接下来是数据更新时会调用的钩子函数
function flushSchedulerQueue() {
// ...
for (index = 0; index < queue.length; index++) {
watcher = queue[index];
if (watcher.before) {
watcher.before(); // 调用 beforeUpdate
}
id = watcher.id;
has[id] = null;
watcher.run();
// in dev build, check and stop circular updates.
if (process.env.NODE_ENV !== "production" && has[id] != null) {
circular[id] = (circular[id] || 0) + 1;
if (circular[id] > MAX_UPDATE_COUNT) {
warn(
"You may have an infinite update loop " +
(watcher.user
? `in watcher with expression "${watcher.expression}"`
: `in a component render function.`),
watcher.vm
);
break;
}
}
}
callUpdatedHooks(updatedQueue);
}
function callUpdatedHooks(queue) {
let i = queue.length;
while (i--) {
const watcher = queue[i];
const vm = watcher.vm;
if (vm._watcher === watcher && vm._isMounted) {
callHook(vm, "updated");
}
}
}
上图还有两个生命周期没有说,分别为 activated 和 deactivated ,这两个钩子函数是 keep-alive 组件独有的。用 keep-alive 包裹的组件在切换时不会进行销毁,而是缓存到内存中并执行 deactivated 钩子函数,命中缓存渲染后会执行 activated 钩子函数。
最后就是销毁组件的钩子函数了
Vue.prototype.$destroy = function () {
// ...
callHook(vm, "beforeDestroy");
vm._isBeingDestroyed = true;
// remove self from parent
const parent = vm.$parent;
if (parent && !parent._isBeingDestroyed && !vm.$options.abstract) {
remove(parent.$children, vm);
}
// teardown watchers
if (vm._watcher) {
vm._watcher.teardown();
}
let i = vm._watchers.length;
while (i--) {
vm._watchers[i].teardown();
}
// remove reference from data ob
// frozen object may not have observer.
if (vm._data.__ob__) {
vm._data.__ob__.vmCount--;
}
// call the last hook...
vm._isDestroyed = true;
// invoke destroy hooks on current rendered tree
vm.__patch__(vm._vnode, null);
// fire destroyed hook
callHook(vm, "destroyed");
// turn off all instance listeners.
vm.$off();
// remove __vue__ reference
if (vm.$el) {
vm.$el.__vue__ = null;
}
// release circular reference (#6759)
if (vm.$vnode) {
vm.$vnode.parent = null;
}
};
在执行销毁操作前会调用 beforeDestroy 钩子函数,然后进行一系列的销毁操作,如果有子组件的话,也会递归销毁子组件,所有子组件都销毁完毕后才会执行根组件的 destroyed 钩子函数。
Vue 的父组件和子组件生命周期钩子执行顺序是什么
父组件: beforeCreate -> created -> beforeMount 子组件: -> beforeCreate -> created -> beforeMount -> mounted 父组件: -> mounted
父组件先 created 后 mounted 子组件后 created 先 mounted
总结:从外到内,再从内到外
组件 Keep Alive
组件 keep-alive 直接将组件缓存在了内存中,而没有直接走入销毁阶段。
如果你需要在组件切换的时候,保存一些组件的状态防止多次渲染,就可以使用 keep-alive 组件包裹需要保存的组件。
在 vue 2.1.0 版本之后,keep-alive 新加入了两个属性: include(包含的组件缓存) 与 exclude(排除的组件不缓存,优先级大于 include)。
对于 keep-alive 组件来说,它拥有两个独有的生命周期钩子函数,分别为 activated 和 deactivated 。用 keep-alive 包裹的组件在切换时不会进行销毁,而是缓存到内存中并执行 deactivated 钩子函数,命中缓存渲染后会执行 activated 钩子函数。
if (this.keep - alive) {
var cached = this.cache[id];
}
什么时候用到 beforeDestroy?
在 beforeCreated 中发出请求合适吗?会出现什么问题?
获取不到 props 和 data
vue created 和 mounted 的异同。
很多人会觉得,异步请求函数放在 created 钩子中会比放在 mounted 钩子中页面加载速度更快,其实这是没有事实依据的。
created 钩子函数的执行标志着事件循环系统、初始化 props,data 、渲染函数的初始化等工作的完成,但是还没有开始真正的渲染节点。而 mounted 钩子的执行标志着节点的渲染已经结束,并且已经挂载节点到 dom 上去了,此时获取 dom 节点是可以获取到的,反之在 mounted 之前的钩子函数中都是获取不到的。
created 钩子中不能够直接获取 dom 因为 dom 够没有挂载,document.getElementById 之类的方法都不一定会生效。
需要注意的是,mounted 钩子函数是将 vdom 形成真实的 dom 节点并挂载到父组件上去。所以在一个组件 mounted 钩子函数之前,都不能获取真实的 dom 节点,比如 document.querySelector(".content") 或者 getELementById ,包括 this.$el 也是。(本来以为 this.$el 是会有的,结果也是 null, 需要继续去看一下 this.$el 是什么时候挂载的)。啊, 到底是什么时候形成 dom 树的 ?
为什么在 child 的 mounted 钩子中,能够获取到 parent 的 dom 了?
await created 为什么没有同步执行
async 只能保证函数内部存在的 await 语法函数能够同步执行,但是并不能保证整个 created 函数执行完了之后才会执行后面的操作。
Promise 不会阻碍其他的任务执行。
几种通信原理
父子组件通信
- emit/props
$parent.xxx- provide/injected
- event bus
- Vuex
任意组件
这种方式可以通过 Vuex 或者 Event Bus 解决,另外如果你不怕麻烦的话,可以使用这种方式解决上述所有的通信情况